﻿using System;
using Rainbow.Exceptions;
using Rainbow.ObjectFlow.Engine;
using Rainbow.ObjectFlow.Interfaces;
using Rainbow.ObjectFlow.Language;
using Rainbow.ObjectFlow.Policies;
using System.Linq;

namespace Rainbow.ObjectFlow.Framework
{
    /// <summary>
    /// Pipelines are composed of generic IOperations.  A pipeline
    /// controls the workflow whereas the IOperation encapsalates logic.
    /// </summary>
    public class Workflow<T> : IWorkflow<T>, IDefine<T>, ICompose<T>, IMerge<T>, IExecuteWorkflow<T> where T : class
    {
        private WorkflowBuilder<T> _workflowBuilder;
        private readonly Dispatcher<T> _workflowEngine;
        private TaskList<T> _tasklist;
        private AndOperand<T> _andOperand;
        /// <summary>
        /// default constructor
        /// </summary>
        public Workflow()
        {
            _workflowEngine = new Dispatcher<T>();
            _workflowBuilder = new SequentialBuilder<T>();
            _tasklist = new TaskList<T>();
            _workflowBuilder.OperationAdded += _tasklist.OperationAdded;
            ExceptionHandler = new ExceptionHandler();
        }

        internal Workflow(WorkflowBuilder<T> builder)
        {
            _workflowBuilder = builder;
        }

        internal Workflow(Dispatcher<T> workflowEngine, TaskList<T> tasklist)
            : this()
        {
            _workflowEngine = workflowEngine;
            _tasklist = tasklist;
            _workflowBuilder.OperationAdded += _tasklist.OperationAdded;
        }

        /// <summary>
        /// Joins one operation onto another to run in parallel
        /// <remarks>
        /// Although data will be passed to operations or functions running in parallel 
        /// these operations will not affect the data passed to subsequent operations.  This was to reduce the 
        /// complexity of parallel operations in Version 1.x and this behaviour may be extended to 
        /// pass state in the future.
        /// </remarks>
        /// </summary>
        public IAnd<T> And
        {
            get
            {
                if (IsSequentialBuilder(_workflowBuilder))
                {
                    var operation = RemoveLastOperation();
                    _andOperand = CreateAndInitialiseAndJoin(operation);
                }

                return _andOperand;
            }
        }

        private AndOperand<T> CreateAndInitialiseAndJoin(OperationDuplex<T> operation)
        {
            _workflowBuilder = new ParallelSplitBuilder<T>();
            _workflowBuilder.OperationAdded += _tasklist.OperationAdded;
            _andOperand = new AndOperand<T>(this, _workflowBuilder);
            _andOperand.Do(operation);

            return _andOperand;
        }

        private OperationDuplex<T> RemoveLastOperation()
        {
            // TODO: and should probably subscribe to events and raise it to workflow builder. this would decouple the builder from the and.
            var operation = _tasklist.Tasks[_tasklist.Tasks.Count - 1];
            _tasklist.Tasks.RemoveAt(_tasklist.Tasks.Count - 1);
            return operation;
        }

        /// <summary>
        /// Gets or sets the context of the workflow.
        /// </summary>
        /// <value>The context for the workflow operations.</value>
        public T Context
        {
            get { return _workflowEngine.Context; }
            set { _workflowEngine.Context = value; }
        }

        //TODO remove?
        internal TaskList<T> RegisteredOperations
        {
            get { return _tasklist; }
        }

        internal ExceptionHandler ExceptionHandler { get; set; }

        /// <summary>
        /// Adds an operation into the workflow definition
        /// </summary>
        /// <param name="operation">The operation to execute as a generic of IOperation</param>
        /// <remarks>Operations must inherit from the BasicOperation class</remarks>        
        public virtual IWorkflow<T> Do(IOperation<T> operation)
        {
            Check.IsNotNull(operation, "operation");
            Check.IsInstanceOf<BasicOperation<T>>(operation, "operation");

            _workflowBuilder.AddOperation(operation);

            return this;
        }

        /// <summary>
        /// Adds an operation into the workflow definition given the constraint.  The constraint runs if
        /// the constraint evaluates to true.
        /// </summary>
        /// <param name="operation">The operation to execute as a generic of IOperation</param>
        /// <param name="constraint">The constraint to evaluate</param>
        /// <remarks>Operations must inherit from the BasicOperation class</remarks>
        public virtual IWorkflow<T> Do(IOperation<T> operation, ICheckConstraint constraint)
        {
            Check.IsNotNull(operation, "operation");
            Check.IsNotNull(constraint, "constraint");
            Check.IsInstanceOf<BasicOperation<T>>(operation, "operation");

            _workflowBuilder.AddOperation(operation, constraint);

            return this;
        }

        /// <summary>
        /// Adds a function into the workflow definition
        /// </summary>
        /// <param name="function">Function to add</param>
        public IWorkflow<T> Do(Func<T, T> function)
        {
            Check.IsNotNull(function, "function");

            _workflowBuilder.AddOperation(function);
            return this;
        }

        /// <summary>
        /// Adds a function into the workflow definition
        /// </summary>
        /// <param name="function">Function to add</param>
        /// <param name="constraint">constraint defines if function will be executed</param>
        public IWorkflow<T> Do(Func<T, T> function, ICheckConstraint constraint)
        {
            Check.IsNotNull(function, "function");
            Check.IsNotNull(constraint, "constraint");

            _workflowBuilder.AddOperation(function, constraint);
            return this;
        }

        /// <summary>
        /// Begins the execution of a workflow
        /// </summary>
        /// <returns>The data after being transformed by the Operation objects</returns>
        /// <remarks>
        /// This is only valid for state objects where operations on null can be performed as this delegates to Start(T data) by passing it null. 
        /// </remarks>
        public virtual T Start()
        {
             return Start(default(T));
        }

        /// <summary>
        /// Runs the workflow definition
        /// </summary>
        /// <param name="data">Data to transform</param>
        /// <returns>Result of the workflow</returns>
        public virtual T Start(T data)
        {
            Then();
            ExceptionHandler.When(() => _workflowEngine.Execute(_tasklist.Tasks, data));
            return Context;
        }

        /// <summary>
        /// Subsequent Operations and functions will execute after the previous one has completed.
        /// </summary>
        public virtual IMerge<T> Then()
        {
            if (!IsSequentialBuilder(_workflowBuilder))
            {
                _tasklist.Tasks.Add(new OperationDuplex<T>(_workflowBuilder.ParallelOperations));

                _workflowBuilder = new SequentialBuilder<T>();

                //SetCurrentTasksFromList(_tasklist.Tasks.ToArray());
                _workflowBuilder.OperationAdded += _tasklist.OperationAdded;
            }

            return this;
        }

        private static bool IsSequentialBuilder(WorkflowBuilder<T> builder)
        {
            return null == builder || (typeof(SequentialBuilder<T>) == builder.GetType());
        }

        /// <summary>
        /// Instantiates a workflow object
        /// </summary>
        public static IDefine<T> Definition()
        {
            return new Workflow<T>();
        }

        /// <summary>
        /// Adds a sub-workflow into the workflow definition
        /// </summary>
        /// <param name="workflow">Workflow to add</param>
        public IWorkflow<T> Do(IExecuteWorkflow<T> workflow)
        {
            Check.IsNotNull(workflow, "workflow");
            _workflowBuilder.AddOperation(workflow);

            return this;
        }

        /// <summary>
        /// Adds a sub-workflow into the workflow definition
        /// </summary>
        /// <param name="workflow">Workflow to add</param>
        /// <param name="constraint">pre-condition for execution</param>
        public IWorkflow<T> Do(IExecuteWorkflow<T> workflow, ICheckConstraint constraint)
        {
            Check.IsNotNull(workflow, "workflow");
            Check.IsNotNull(constraint, "constraint");

            _workflowBuilder.AddOperation(workflow, constraint);

            return this;
        }

        /// <summary>
        /// Allows an operation to be attempted again
        /// </summary>
        public IRetryPolicy Retry()
        {
            IRetryPolicy policy = new Retry(this);
            if (_tasklist.Tasks.Count > 0)
            {
                _tasklist.Tasks[_tasklist.Tasks.Count - 1].Command.Policies.Add(policy);
            }

            return policy;
        }

        /// <summary>
        /// Does the operation again.  Unlike Retry, this does not check on success or failure
        /// </summary>
        /// <returns></returns>
        public IRepeat Repeat()
        {
            IRepeat policy = new Repeat(this);
            if (_tasklist.Tasks.Count > 0)
            {
                _tasklist.Tasks[_tasklist.Tasks.Count - 1].Command.Policies.Add(policy);
            }

            return policy;
        }

        /// <summary>
        /// Registers an instance of the type specified in the workflow
        /// </summary>
        /// <typeparam name="TOperation">Type that inherits from BasicOperation of T</typeparam>
        public IWorkflow<T> Do<TOperation>() where TOperation : BasicOperation<T>
        {
            _workflowBuilder.AddOperation<TOperation>();

            return this;
        }

        /// <summary>
        /// Registers an instance of the type specified in the workflow
        /// </summary>
        /// <typeparam name="TOperation">Type that inherits from BasicOperation of T</typeparam>
        /// <param name="constraint">The constraint to evaluate</param>
        public IWorkflow<T> Do<TOperation>(ICheckConstraint constraint) where TOperation : BasicOperation<T>
        {
            _workflowBuilder.AddOperation<TOperation>(constraint);

            return this;
        }

        //        public static IHandle Configure()
        //        {
        //            return new ExceptionHandler();
        //        }
    }
}